﻿// Copyright (C) Norsolutions GmbH
// All rights reserved.
// For conditions of distribution and use, see license.
// Created by: Reng van Oord
// Created:    2010.05.05


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using Sherwood.Localization;

namespace Sherwood.Mvc.Localization
{
    /// <summary>
    /// ASP.Net output filter implementation which replaces language strings ([!string!]) 
    /// with the corrosponding translation from the applications translation dictionary. 
    /// </summary>
    public class TranslationOutputFilter : OutputFilterBase
    {
        private string _carryOver = "";
        Dictionary<string, string> _translationDictionary = new Dictionary<string, string>();
        private int _asyncReturnCount = 0;
        private ITranslationProvider _provider;

        public TranslationOutputFilter(Stream stream, ITranslationProvider provider)
            : base(stream)
        {
            _provider = provider;
        }



        public override void Write(byte[] buffer, int offset, int count)
        {
            byte[] translatedBuffer = Encoding.UTF8.GetBytes(Translate(Encoding.UTF8.GetString(buffer, 0, count)));
            _sink.Write(translatedBuffer, offset, translatedBuffer.Length);
        }

        /// <summary>
        /// Provides a means of translating a single block of text containing language string in the
        /// format [!string!].
        /// This method can be used outside the context of a stream filter.
        /// </summary>
        /// <param name="toTranslate">Text containing tranlation strings</param>
        /// <returns>Translated text</returns>
        public static string TranslateSingleBlock(string toTranslate, ITranslationProvider provider)
        {
            MemoryStream dummyStream = new MemoryStream();
            TranslationOutputFilter filter = new TranslationOutputFilter(dummyStream, provider);
            return filter.Translate(toTranslate);
        }

        /// <summary>
        /// Replaces all language string in the format [!key!] with their translation 
        /// </summary>
        /// <param name="toTranslate">String to perform translations in.</param>
        /// <returns>Translated text</returns>
        public string Translate(string toTranslate)
        {
            toTranslate = _carryOver + toTranslate;

            //Ensure provider exists
            if (_provider == null)
                return toTranslate;
            
            //Find all text to be translated and put it into a dictionary along with translation.
            Regex reTranslate = new Regex("\\[\\!(((?!\\!\\]).)+)\\!\\]", RegexOptions.Singleline);
            MatchCollection matches = reTranslate.Matches(toTranslate);
            _asyncReturnCount = 0;
            int totalRequestCount = 0;
            foreach (Match match in matches)
            {
                if (!string.IsNullOrEmpty(match.Value))
                {
                    string translationExpression = match.Value;
                    string key = translationExpression.Replace("[!", "").Replace("!]", "");
                    if (!_translationDictionary.ContainsKey(key))
                    {
                        if (!string.IsNullOrEmpty(key))
                        {
                            lock (this)
                            {
                                _translationDictionary.Add(key, key); //add default value
                                _asyncReturnCount++;
                                totalRequestCount++;
                            }
                            _provider.GetStringAsync(key, key, AddTranslation, null);
                        }
                    }
                }
            }

            //Wait until all strings have been translated (_asyncReturnCount should be 0 at this point).
            const int firstResponseTimeoutInSeconds = 2;
            const int timeoutInSeconds = 20;
            DateTime firstResponseTimeout = DateTime.Now.AddSeconds(firstResponseTimeoutInSeconds);
            DateTime timeout = DateTime.Now.AddSeconds(timeoutInSeconds);
            while (_asyncReturnCount > 0)
            {
                if (_asyncReturnCount == totalRequestCount) //if no translations have been returned
                {
                    if (DateTime.Now > firstResponseTimeout)
                    {
                        break; //assume error with translation provider.
                    }
                }
                if (DateTime.Now < timeout)
                {
                    System.Threading.Thread.Sleep(10);
                }
                else
                {
                    break; //assume error with translation provider (page may be partially translated).
                }
            }

            //Apply translations to string
            StringBuilder translatedOutput = new StringBuilder(toTranslate);
            foreach (string key in _translationDictionary.Keys)
            {
                string toReplace = "[!" + key + "!]";
                translatedOutput.Replace(toReplace, _translationDictionary[key]);
            }
            string returnValue = translatedOutput.ToString();


            //Attempt to find the first half of a translation expression at the end of the string
            //This is saved into a variable which is added to the beginning of the next string to pass through the filter. 
            _carryOver = "";
            Regex reHalfTranslate = new Regex("\\[(\\!(((?!\\!\\]).)*))?$", RegexOptions.Singleline);
            Match carryOverMatch = reHalfTranslate.Match(returnValue);
            if (carryOverMatch != null && !String.IsNullOrEmpty(carryOverMatch.Value))
            {
                returnValue = returnValue.Replace(carryOverMatch.Value, "");
                _carryOver = carryOverMatch.Value;
            }

            return returnValue;
        }

        public void AddTranslation(string key, string translation, object state)
        {
            lock (this)
            {
                _translationDictionary[key] = translation;
                _asyncReturnCount--;
            }
        }

    }
}
